package Controle;

import Modelo.Tabela;
import Modelo.Tokens;
import java.util.LinkedList;
import java.util.Stack;
import javax.swing.JOptionPane;

public class Semantico {

    LinkedList<Tabela> simbolos = new LinkedList<>();
    LinkedList<String> retorno = new LinkedList<>();
    public float conta = 0;
    boolean naoEntraELSE = true;

    public LinkedList<String> semanticozeia(LinkedList<Tokens> tokens) {
        int i = 2;

        //DECLARACOES
        while (!("}".equals(tokens.get(i).getLexema().toString()))) {
            if ("palavra_reservada".equals(tokens.get(i).getTipo())) {
                String tipo = tokens.get(i).getLexema().toString();
                i++;
                while (!("palavra_reservada".equals(tokens.get(i).getTipo()) || "}".equals(tokens.get(i).getLexema().toString()))) {
                    if ("identificador".equals(tokens.get(i).getTipo())) {
                        //cria entrada na tabela de simboblos
                        if ("BOOLEANO".equals(tipo)) {
                            simbolos.add(new Tabela((String) tokens.get(i).getLexema(), tipo, "false"));
                        } else {
                            if ("STRING".equals(tipo)) {
                                simbolos.add(new Tabela((String) tokens.get(i).getLexema(), tipo, ""));
                            } else {
                                simbolos.add(new Tabela((String) tokens.get(i).getLexema(), tipo, "0"));
                            }
                        }
                    }
                    i++;
                }
            }
        }
        //PROGRAMA
        //é o primeiro tokem com informação do programa
        i += 4;
        i = implementaini(i, tokens);

//        for (int j = 0; j < simbolos.size(); j++) {
//            System.out.println(simbolos.get(j).toString());
//        }
        return retorno;
    }

    private int implementaini(int pos, LinkedList<Tokens> tokens) {

        while (!"}".equals(tokens.get(pos).getLexema()) && !"ROTINA".equals(tokens.get(pos).getLexema())) {
            
            //Verifica se o tokem lido eh um if
            if ("IF".equals(tokens.get(pos).getLexema())) {
                pos = chamaIF(pos, tokens);
            }
            
            if ("ELSE".equals(tokens.get(pos).getLexema()) && naoEntraELSE) {
                pos = chamaELSE(pos, tokens);
            }

            //Veririca se o tokem é um identificador
            if ("identificador".equals(tokens.get(pos).getTipo())) {
                pos = chamaIDENTIFICADOR(pos, tokens);
            }

            //Veririca se o tokem é um identificador
            if ("FOR".equals(tokens.get(pos).getLexema())) {
                pos = chamaFOR(pos, tokens);
            }

            //Veririca se o tokem é um identificador
            if ("WHILE".equals(tokens.get(pos).getLexema())) {
                pos = chamaWHILE(pos, tokens);
            }

            //Veririca se o tokem é um identificador
            if ("ENTRA".equals(tokens.get(pos).getLexema())) {
                pos = chamaENTRA(pos, tokens);
            }

            //Veririca se o tokem é um identificador
            if ("SAI".equals(tokens.get(pos).getLexema())) {
                pos = chamaSAI(pos, tokens);
            }

            pos++;
        }
        return pos;
    }

    private int chamaIDENTIFICADOR(int pos, LinkedList<Tokens> T) {
        String var = T.get(pos).getLexema() + "";
        int onde = pos;
        pos++;
        if (T.get(pos).getLexema().toString().equals("=")) {
            pos = chamaATRIBUICAO(pos, T); // posição global? retornar conta pra var!
            for (int h = 0; h < simbolos.size(); h++) {
                if (T.get(onde).getLexema().toString().equals(simbolos.get(h).getNome())) {
                    simbolos.get(h).setValor(conta + "");
                    h = simbolos.size();
                    onde = -1;
                }
                if (onde != -1) {
                    retorno.add("Erro! variável " + T.get(pos).getLexema().toString() + " não declarada!");
                }
            }
            // achar var!
        } else {
            pos += 3;//(){
            chamaFUNCAO(var, pos, T);
        }
        return pos;
    }

    private int chamaFOR(int pos, LinkedList<Tokens> T) {
        pos += 2;// ( e para em ID
        int x = 0, posicao = 0;
        int counter = 0, condiA = 0, condiB = 0, inc = 0;
        String op = "";
        for (int j = 0; j < simbolos.size(); j++) {
            if (T.get(pos).getLexema().toString().equals(simbolos.get(j).getNome())) {
                if (simbolos.get(j).getTipo().toString().equals("INTEIRO")) {
                    x = j = simbolos.size();
                }
            }
        }
        if (x == 0) {
            retorno.add("Erro! esperado tipo INTEIRO na variável " + T.get(pos).getLexema());
        }
        x = 0;
        pos += 2; //= e para em IDdig
        if (T.get(pos).getTipo().toString().equals("identificador")) {
            for (int j = 0; j < simbolos.size(); j++) {
                if (T.get(pos).getLexema().toString().equals(simbolos.get(j).getNome())) {
                    posicao = j;
                    if (simbolos.get(j).getTipo().toString().equals("INTEIRO")) {
                        counter = getValorInteiro(pos, T);
                    } else {
                        counter = (int) getValorReal(pos, T);
                    }
                    x = j = simbolos.size();
                }
            }
            if (x == 0) {
                retorno.add("Erro! esperado tipo INTEIRO na variável " + T.get(pos).getLexema());
            }
        } else {
            counter = (int) Integer.parseInt(T.get(pos).getLexema() + "");
        }
        simbolos.get(posicao).setValor(counter + "");
        pos += 2; // le ; e para em ID
        for (int j = 0; j < simbolos.size(); j++) {
            if (T.get(pos).getLexema().toString().equals(simbolos.get(j).getNome())) {
                if (simbolos.get(j).getTipo().toString().equals("INTEIRO")) {
                    posicao = j;
                    condiA = getValorInteiro(pos, T);
                    x = j = simbolos.size();
                }
            }
        }
        if (x == 0) {
            retorno.add("Erro! esperado tipo INTEIRO na variável " + T.get(pos).getLexema());
        }
        x = 0;
        pos++; //le op log
        if (T.get(pos).getTipo().toString().equals("logico")) {
            op = T.get(pos).getLexema() + "";
        } else {
            retorno.add("Erro! esperado operador logico no lugar de " + T.get(pos).getLexema());
        }
        pos++; // id dig
        if (T.get(pos).getTipo().toString().equals("identificador")) {
            for (int j = 0; j < simbolos.size(); j++) {
                if (T.get(pos).getLexema().toString().equals(simbolos.get(j).getNome())) {
                    if (simbolos.get(j).getTipo().toString().equals("INTEIRO")) {
                        condiB = getValorInteiro(pos, T);
                    } else {
                        condiB = (int) getValorReal(pos, T);
                    }
                    x = j = simbolos.size();
                }
            }
            if (x == 0) {
                retorno.add("Erro! esperado tipo INTEIRO na variável " + T.get(pos).getLexema());
            }
        } else {
            condiB = (int) Integer.parseInt(T.get(pos).getLexema() + "");
        }
        x = 0;
        pos += 2;
        if (T.get(pos).getTipo().toString().equals("identificador")) {
            for (int j = 0; j < simbolos.size(); j++) {
                if (T.get(pos).getLexema().toString().equals(simbolos.get(j).getNome())) {
                    if (simbolos.get(j).getTipo().toString().equals("INTEIRO")) {
                        inc = getValorInteiro(pos, T);
                    } else {
                        inc = (int) getValorReal(pos, T);
                    }
                    x = j = simbolos.size();
                }
            }
            if (x == 0) {
                retorno.add("Erro! esperado tipo numerico na variável " + T.get(pos).getLexema());
            }
        } else {
            inc = (int) Integer.parseInt(T.get(pos).getLexema() + "");
        }
        pos += 3;// termmina o if
        int saida = pos;
        while (valida(condiA, op, condiB) == true) {
            saida = implementaini(pos, T);
            condiA += inc;
            simbolos.get(posicao).setValor(condiA + "");
        }
        return saida;
    }

    private int chamaWHILE(int pos, LinkedList<Tokens> T) {
        int A = 0;
        float B = 0;
        int x = 0, posicaoA = 0;
        String op = null;
        pos += 2;//ta no IF, le ( e para no ID
        for (int j = 0; j < simbolos.size(); j++) {
            if (T.get(pos).getLexema().toString().equals(simbolos.get(j).getNome())) {
                if (simbolos.get(j).getTipo().toString().equals("INTEIRO")) {
                    posicaoA = j;
                    A = getValorInteiro(pos, T);
                    x = j = simbolos.size();
                }
            }
        }
        if (x == 0) {
            retorno.add("Erro! esperado tipo INTEIRO na variável " + T.get(pos).getLexema());
        }
        x = 0;
        pos++; //le op log
        if (T.get(pos).getTipo().toString().equals("logico")) {
            op = T.get(pos).getLexema() + "";
        } else {
            retorno.add("Erro! esperado operador logico no lugar de " + T.get(pos).getLexema());
        }
        pos++; // id dig
        if (T.get(pos).getTipo().toString().equals("identificador")) {
            for (int j = 0; j < simbolos.size(); j++) {
                if (T.get(pos).getLexema().toString().equals(simbolos.get(j).getNome())) {
                    if (simbolos.get(j).getTipo().toString().equals("INTEIRO")) {
                        B = getValorInteiro(pos, T);
                    } else {
                        B = (int) getValorReal(pos, T);
                    }
                    x = j = simbolos.size();
                }
            }
            if (x == 0) {
                retorno.add("Erro! esperado tipo INTEIRO na variável " + T.get(pos).getLexema());
            }
        } else {
            B = (int) Integer.parseInt(T.get(pos).getLexema() + "");
        }
        pos += 3;
        int fimWhile = pos;
        while (valida(A, op, B)) {
            fimWhile = implementaini(pos, T);
            A = (int) Float.parseFloat(simbolos.get(posicaoA).getValor().toString());
        }
        return fimWhile;
    }

    private int chamaENTRA(int pos, LinkedList<Tokens> tokens) {
        pos += 2;
        boolean existe = false;
        if (tokens.get(pos).getTipo().toString().equals("identificador")) {
            for (int j = 0; j < simbolos.size(); j++) {
                if (tokens.get(pos).getLexema().toString().equals(simbolos.get(j).getNome())) {
                    existe = true;
                    String entrada = JOptionPane.showInputDialog("Digite uma entrada.");

                    simbolos.get(j).setValor(entrada);
                }
            }
            if (!existe) {
                retorno.add("Erro!: variavel " + tokens.get(pos).getLexema().toString() + " não declarada!");
            }
        }
        return pos;
    }

    private int chamaSAI(int pos, LinkedList<Tokens> tokens) {
        String aux = "";
        pos += 2;
        while (!tokens.get(pos).getLexema().toString().equals(")")) {
            if ((tokens.get(pos).getTipo().toString().equals("txt_string")) || (tokens.get(pos).getTipo().toString().equals("identificador"))) {
                if (tokens.get(pos).getTipo().toString().equals("identificador")) {
                    int x = 0;
                    for (int j = 0; j < simbolos.size(); j++) {
                        if (tokens.get(pos).getLexema().toString().equals(simbolos.get(j).getNome())) {
                            aux += simbolos.get(j).getValor() + "";
                            x = (j = pos);
                        }
                    }
                    if (x == 0) {
                        retorno.add("Erro!: variavel " + tokens.get(pos).getLexema().toString() + " não declarada!");
                    }
                } else {
                    aux += tokens.get(pos).getLexema();
                }
            } else {
                aux += " ";
            }
            pos++;
        }
        retorno.add(aux);
        return pos;
    }

    public int chamaIF(int pos, LinkedList<Tokens> T) {
        int A = 0;
        float B = 0;
        int x = 0;
        String op = null;
        pos += 2;//ta no IF, le ( e para no ID
        for (int j = 0; j < simbolos.size(); j++) {
            if (T.get(pos).getLexema().toString().equals(simbolos.get(j).getNome())) {
                if (simbolos.get(j).getTipo().toString().equals("INTEIRO")) {
                    A = getValorInteiro(pos, T);
                    x = j = simbolos.size();
                }
            }
        }
        if (x == 0) {
            retorno.add("Erro! esperado tipo INTEIRO na variável " + T.get(pos).getLexema());
        }
        x = 0;
        pos++; //le op log
        if (T.get(pos).getTipo().toString().equals("logico")) {
            op = T.get(pos).getLexema() + "";
        } else {
            retorno.add("Erro! esperado operador logico no lugar de " + T.get(pos).getLexema());
        }
        pos++; // id dig
        if (T.get(pos).getTipo().toString().equals("identificador")) {
            for (int j = 0; j < simbolos.size(); j++) {
                if (T.get(pos).getLexema().toString().equals(simbolos.get(j).getNome())) {
                    if (simbolos.get(j).getTipo().toString().equals("INTEIRO")) {
                        B = getValorInteiro(pos, T);
                    } else {
                        B = (int) getValorReal(pos, T);
                    }
                    x = j = simbolos.size();
                }
            }
            if (x == 0) {
                retorno.add("Erro! esperado tipo INTEIRO na variável " + T.get(pos).getLexema());
            }
        } else {
            B = (int) Integer.parseInt(T.get(pos).getLexema() + "");
        }
        pos += 3;
        if (valida(A, op, B)) {
            naoEntraELSE = true;
            pos = implementaini(pos, T);
        } else {
            pos--;
            Stack<String> pilhaAbreChaves = new Stack<>();
            while (true) {
                if (T.get(pos).getLexema().equals("{")) {
                    System.out.println("Acho um abre {");
                    pilhaAbreChaves.push(T.get(pos).getLexema().toString());
                } else {
                    if (T.get(pos).getLexema().equals("}")) {
                        System.out.println("Acho um fecha }");
                        if (pilhaAbreChaves.size() == 1) {
                            naoEntraELSE = false;
                            pos++;
                            return pos = implementaini(pos, T);
                        } else {
                            pilhaAbreChaves.pop();
                        }
                    }
                }
                pos++;
            }
        }
        return pos;
    }

    public int chamaELSE(int pos, LinkedList<Tokens> T) {
        pos++;
        Stack<String> pilhaAbreChaves = new Stack<>();
        while (true) {
            if (T.get(pos).getLexema().equals("{")) {
                System.out.println("Acho um abre {");
                pilhaAbreChaves.push(T.get(pos).getLexema().toString());
            } else {
                if (T.get(pos).getLexema().equals("}")) {
                    System.out.println("Acho um fecha }");
                    if (pilhaAbreChaves.size() == 1) {
                        return pos;
                    } else {
                        pilhaAbreChaves.pop();
                    }
                }
            }
            pos++;
        }
    }

    public boolean valida(float A, String X, float B) {

        if ("==".equals(X)) {
            if (A == B) {
                return true;
            } else {
                return false;
            }
        }
        if ("<=".equals(X)) {
            if (A <= B) {
                return true;
            } else {
                return false;
            }
        }
        if (">=".equals(X)) {
            if (A >= B) {
                return true;
            } else {
                return false;
            }
        }

        if (">".equals(X)) {
            if (A > B) {
                return true;
            } else {
                return false;
            }
        }

        if ("<".equals(X)) {
            if (A < B) {
                return true;
            } else {
                return false;
            }
        }

        if ("!=".equals(X)) {
            if (A != B) {
                return true;
            } else {
                return false;
            }
        }
        retorno.add("Erro! operador lógico esperado no lugar de " + X + "!");
        return false;
    }

    public int getValorInteiro(int pos, LinkedList<Tokens> T) {
        int x = 0;
        float aux = 0;
        for (int j = 0; j < simbolos.size(); j++) {
            if (T.get(pos).getLexema().toString().equals(simbolos.get(j).getNome())) {
                aux = Float.parseFloat(simbolos.get(j).getValor() + "");
                x = (j = pos);
            }
        }
        if (x == 0) {
            retorno.add("Erro!: variavel " + T.get(pos).getLexema().toString() + " não declarada!");
            return 0;
        }

        return (int) aux;
    }

    public float getValorReal(int pos, LinkedList<Tokens> T) {
        int x = 0;
        float aux = 0;
        for (int j = 0; j < simbolos.size(); j++) {
            if (T.get(pos).getLexema().toString().equals(simbolos.get(j).getNome())) {
                aux = Float.parseFloat(simbolos.get(j).getValor() + "");
                x = (j = pos);
            }
        }
        if (x == 0) {
            retorno.add("Erro!: variavel " + T.get(pos).getLexema().toString() + " não declarada!");
            return 0;
        }
        return aux;
    }

    public boolean getValorBooleano(int pos, LinkedList<Tokens> T) {
        int x = 0;
        boolean aux = true;
        for (int j = 0; j < simbolos.size(); j++) {
            if (T.get(pos).getLexema().toString().equals(simbolos.get(j).getNome())) {
                aux = Boolean.parseBoolean(simbolos.get(j).getValor() + "");
                x = (j = pos);
            }
        }
        if (x == 0) {
            retorno.add("Erro!: variavel " + T.get(pos).getLexema().toString() + " não declarada!");
            return false;
        }
        return aux;
    }

    private int chamaATRIBUICAO(int pos, LinkedList<Tokens> T) {
        int x = 0;
        pos++;
        if (T.get(pos).getTipo().toString().equals("identificador")) {
            for (int j = 0; j < simbolos.size(); j++) {
                if (T.get(pos).getLexema().toString().equals(simbolos.get(j).getNome())) {
                    if (simbolos.get(j).getTipo().toString().equals("INTEIRO")) {
                        conta = getValorInteiro(pos, T);
                    } else {
                        conta = (int) getValorReal(pos, T);
                    }
                    x = j = simbolos.size();
                }
            }
            if (x == 0) {
                retorno.add("Erro! esperado tipo INTEIRO na variável " + T.get(pos).getLexema());
            }
        } else {
            conta = (int) Integer.parseInt(T.get(pos).getLexema() + "");
        }
        pos++;
        while (!T.get(pos).getLexema().toString().equals(";")) {
            float mais = 0;
            String aux = T.get(pos).getLexema() + "";
            pos++;
            if (T.get(pos).getTipo().toString().equals("identificador")) {
                for (int j = 0; j < simbolos.size(); j++) {
                    if (T.get(pos).getLexema().toString().equals(simbolos.get(j).getNome())) {
                        if (simbolos.get(j).getTipo().toString().equals("INTEIRO")) {
                            mais = getValorInteiro(pos, T);
                        } else {
                            mais = (int) getValorReal(pos, T);
                        }
                        x = j = simbolos.size();
                    }
                }
                if (x == 0) {
                    retorno.add("Erro! esperado tipo INTEIRO na variável " + T.get(pos).getLexema());
                }
            } else {
                mais = (int) Integer.parseInt(T.get(pos).getLexema() + "");
            }
            conta = fazConta(conta, mais, aux, T);
            pos++;
        }
        return pos;
    }

    public float fazConta(float conta, float mais, String aux, LinkedList<Tokens> T) {
        if (aux.equals("+")) {
            conta += mais;
        } else {
            if (aux.equals("-")) {
                conta -= mais;
            } else {
                if (aux.equals("*")) {
                    conta *= mais;
                } else {
                    if (aux.equals("/")) {
                        conta /= mais;
                    } else {
                        if (aux.equals("^")) {
                            conta = (float) Math.pow(conta, mais);
                        } else {
                            if (aux.equals("%")) {
                                conta = conta % mais;
                            } else {
                                retorno.add("Erro! esperado operador aritmético");
                            }
                        }
                    }
                }
            }
        }
        return conta;
    }

    private void chamaFUNCAO(String var, int pos, LinkedList<Tokens> T) {
        int pija = pos;
        pos += 3;// ta em (, le ), ; e para no prox
        try {
            while ((T.get(pija) != null) || (!T.get(pija).getLexema().toString().equals("ROTINA"))) {
                pija++;
                if (T.get(pija).getLexema().toString().equals(var)) {
                    break;
                }
            }
            if (T.get(pija).getLexema().toString().equals(var)) {
                pija += 4; // ROTINA ID ( ) { 
                implementaini(pija, T);
            }
            pija = pos;
        } catch (IndexOutOfBoundsException IOOBE) {
            retorno.add("Erro! rotina não declarada!");
        }
    }
}
/*
 * TESTE FOR
 * * 
VARIAVEIS{ INTEIRO ab; } PROGRAMA Astilililintitin{ FOR(ab = 2 ; ab<10 ;1){
 * SAI(ab); } SAI(ab); }
 */
